package cn.jbolt.core.model.base;

import cn.hutool.core.util.IdUtil;
import cn.jbolt.core.annotation.JBoltAutoCache;
import cn.jbolt.core.annotation.TableBind;
import cn.jbolt.core.annotation.UnProcessBoolean;
import cn.jbolt.core.base.JBoltIDGenMode;
import cn.jbolt.core.base.JBoltMsg;
import cn.jbolt.core.base.config.JBoltConfig;
import cn.jbolt.core.cache.JBoltCacheKit;
import cn.jbolt.core.db.sql.Sql;
import cn.jbolt.core.kit.JBoltSaasTenantKit;
import cn.jbolt.core.kit.JBoltSnowflakeKit;
import cn.jbolt.core.kit.JBoltUserKit;
import cn.jbolt.core.model.User;
import cn.jbolt.core.para.JBoltParaValidator;
import cn.jbolt.core.util.JBoltArrayUtil;
import cn.jbolt.core.util.JBoltDateUtil;
import com.alibaba.fastjson.JSON;
import com.jfinal.kit.StrKit;
import com.jfinal.log.Log;
import com.jfinal.plugin.activerecord.*;
import com.jfinal.plugin.activerecord.dialect.OracleDialect;
import com.jfinal.plugin.activerecord.dialect.PostgreSqlDialect;
import com.jfinal.plugin.ehcache.IDataLoader;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;

/**
 * JBolt Base Model
 *
 * @ClassName: JBoltBaseModel
 * @author: JFinal学院-小木 QQ：909854136
 * @date: 2019年12月5日
 */
@SuppressWarnings({"rawtypes", "unchecked"})
public abstract class JBoltBaseModel<M extends JBoltBaseModel<M>> extends Model<M> implements IBean {
    private static final long serialVersionUID = 1304327649429800504L;
    protected static final Log LOG = Log.getLog(JBoltBaseModel.class);
    protected static final Log JBOLT_AUTO_CACHE_LOG = Log.getLog("JBoltAutoCacheLog");
    /**
     * 内置常量 可用于数据库标识boolean true
     */
    protected static final Integer TRUE = 1;
    /**
     * 内置常量 可用于数据库标识boolean false
     */
    protected static final Integer FALSE = 0;
    public static final String COLUMN_CREATE_TIME = "create_time";
    public static final String COLUMN_UPDATE_TIME = "update_time";
    public static final String COLUMN_DELETE_TIME = "delete_time";
    public static final String COLUMN_USER_ID = "user_id";
    public static final String COLUMN_CREATE_USER_ID = "create_user_id";
    public static final String COLUMN_UPDATE_USER_ID = "update_user_id";
    public static final String COLUMN_DELETE_USER_ID = "delete_user_id";
    /**
     * 配置自动缓存处理
     */
    private transient JBoltAutoCache autoCache;
    /**
     * 是否启用Autocache
     */
    private transient boolean isAutoCacheEnable = false;
    /**
     * 是否启用IDCache
     */
    private transient boolean isIdCacheEnable = false;
    /**
     * 是否启用KeyCache
     */
    private transient boolean isKeyCacheEnable = false;
    /**
     * id序列值
     */
    private transient String idSequenceValue;
    /**
     * model 对应的数据库表 不参与序列化和持久化
     */
    private transient Table table;
    /**
     * 是否是复合多主键
     */
    private transient boolean compositePrimaryKey;
    /**
     * 数据源
     */
    private transient String dataSource;
    /**
     * 数据库里模式schema
     */
    private transient String schema;
    /**
     * 数据库名
     */
    private transient String database;
    /**
     * 表名
     */
    private transient String tableName;
    /**
     * 主键字符串 多个用逗号隔开 不参与数据持久化和序列化
     */
    private transient String primaryKey;
    /**
     * 主键生成策略
     */
    private transient String idGenMode;
    /**
     * 主键 数组 多主键情况用到 不参与数据持久化和序列化
     */
    private transient String[] compositePrimaryKeys;
    /**
     * 绑定数据源数据库类型
     */
    private transient String dbType;
    /**
     * 绑定数据源数据库是否有分表
     */
    private transient boolean separate;
    /**
     * 批量更新处用到的KEY 不参与数据持久化和序列化
     */
    private transient String batchUpdateKey;
    /**
     * 对应的Model Class 不参与数据持久化和序列化
     */
    private transient Class<? extends Model> modelClass;
    /**
     * 是否初始化
     */
    private transient boolean inited = false;

    public JBoltBaseModel() {
        _init();
    }

    protected void _init() {
        if (inited) {
            return;
        }
        modelClass = _getUsefulClass();
        JBoltModelConfig.me.addConfigIfNotExist(modelClass);
        _initTableBind();
        _initAutoCache();
        inited = true;
    }

    /**
     * 初始化AutoCache
     */
    private void _initAutoCache() {
        if (!modelClass.isAnnotationPresent(JBoltAutoCache.class)) {
            return;
        }
        autoCache = JBoltModelConfig.me.getJBoltAutoCache(modelClass);
        isAutoCacheEnable = autoCache != null;
        if (isAutoCacheEnable) {
            isIdCacheEnable = autoCache.idCache();
            isKeyCacheEnable = autoCache.keyCache();
        }
    }

    /**
     * 初始化表绑定
     */
    private void _initTableBind() {
        if (!modelClass.isAnnotationPresent(TableBind.class)) {
            return;
        }
        dataSource = JBoltModelConfig.me.getDataSourceConfigName(modelClass);
        schema = JBoltModelConfig.me.getSchema(modelClass);
        database = JBoltModelConfig.me.getDatabase(modelClass);
        primaryKey = JBoltModelConfig.me.getPrimaryKey(modelClass);
        compositePrimaryKeys = JBoltModelConfig.me.getCompositePrimaryKeys(modelClass);
        compositePrimaryKey = compositePrimaryKeys != null;
        dbType = JBoltModelConfig.me.getDbType(modelClass);
        separate = JBoltModelConfig.me.isSeparate(modelClass);
        tableName = JBoltModelConfig.me.getTableName(modelClass);
        idGenMode = JBoltModelConfig.me.getIdGenMode(modelClass);
        idSequenceValue = JBoltModelConfig.me.getIdSequenceStr(modelClass);
    }

    @Override
    public Boolean getBoolean(String attr) {
        Class<? extends Model> useFulclazz = this._getUsefulClass();
        if (useFulclazz.isAnnotationPresent(UnProcessBoolean.class)) {
            UnProcessBoolean unProcessBoolean = useFulclazz.getAnnotation(UnProcessBoolean.class);
            String[] unProValues = unProcessBoolean.value();
            if (unProValues != null && unProValues.length > 0) {
                for (String v : unProValues) {
                    if (v.equalsIgnoreCase(attr)) {
                        return super.getBoolean(attr);
                    }
                }
            }

        }
        Object value = _getAttrs().get(attr);
        if (value == null || value.toString().trim().length() == 0) {
            return null;
        }
        // 如果就是Boolean
        if (value instanceof Boolean) {
            return (Boolean) value;
        }
        // 使用String 判断
        String v = value.toString();
        int len = v.length();
        switch (len) {
            case 1:// 长度是1 并且值是1=true 0=false
                if (v.equals(TRUE.toString())) {
                    return true;
                }
                if (v.equals(FALSE.toString())) {
                    return false;
                }
                break;
            case 4:
                if (v.equalsIgnoreCase("true")) {
                    return true;
                }
                break;
            case 5:
                if (v.equalsIgnoreCase("false")) {
                    return false;
                }
                break;

            default:
                break;
        }
        return (Boolean) value;
    }

    /**
     * 设置属性值
     *
     * @param attr
     * @param value
     * @param skipIfError 如果出现错误 直接跳过
     * @return
     */
    public M set(String attr, Object value, boolean skipIfError) {
        Class<? extends Model> useFulclazz = this._getUsefulClass();
        if (useFulclazz.isAnnotationPresent(UnProcessBoolean.class)) {
            UnProcessBoolean unProcessBoolean = useFulclazz.getAnnotation(UnProcessBoolean.class);
            String[] unProValues = unProcessBoolean.value();
            if (unProValues != null && unProValues.length > 0) {
                for (String v : unProValues) {
                    if (v.equalsIgnoreCase(attr)) {
                        return super.set(attr, value);
                    }
                }
            }

        }
        Table table = _getTable(); // table 为 null 时用于未启动 ActiveRecordPlugin 的场景
        if (table != null && !table.hasColumnLabel(attr) && !skipIfError) {
            String msg = "The attribute name does not exist: \"" + attr + "\"";
            LOG.error(msg);
            throw new ActiveRecordException(msg);
        }
        if (value != null) {
            if (value instanceof Boolean) {
                Class<?> clazz = table.getColumnType(attr);
                if (clazz != Boolean.class) {
                    value = ((Boolean) value) ? TRUE : FALSE;
                }
            } else if (value instanceof String) {
                String v = value.toString();
                int len = v.length();
                if (len == 4 && v.equalsIgnoreCase("true")) {
                    value = TRUE;
                } else if (len == 5 && v.equalsIgnoreCase("false")) {
                    value = FALSE;
                }
            }
        }
        put(attr, value);
        _getModifyFlag().add(attr); // Add modify flag, update() need this flag.
        return (M) this;

    }

    @Override
    public M set(String attr, Object value) {
        return set(attr, value, false);
    }

    /**
     * 获取主键名称字符串,复合主键逗号隔开
     *
     * @return
     */
    public String _getPrimaryKey() {
        return primaryKey;
    }

    /**
     * 获取主键名称数组,复合主键才有
     *
     * @return
     */
    public String[] _getCompositePrimaryKeys() {
        return compositePrimaryKeys;
    }

    /**
     * 判断表是否存在指定字段
     *
     * @param column
     * @return
     */
    public boolean hasColumn(String column) {
        if(column == null || column.trim().length() == 0){return false;}
        Table table = _getTable(true);
        return table.hasColumnLabel(column)||table.hasColumnLabel(column.toUpperCase());
    }


    /**
     * 判断表是否存在任意一个指定字段
     *
     * @param columns
     * @return
     */
    public boolean hasColumnAny(String... columns) {
        if(columns == null || columns.length == 0){return false;}
        Table table = _getTable(true);
        for(String column:columns){
            if(table.hasColumnLabel(column)||table.hasColumnLabel(column.toUpperCase())){
                return true;
            }
        }
        return false;
    }
    /**
     * 判断表是否存在所有指定字段
     *
     * @param columns
     * @return
     */
    public boolean hasColumnAll(String... columns) {
        if(columns == null || columns.length == 0){return false;}
        Table table = _getTable(true);
        for(String column:columns){
            if(!table.hasColumnLabel(column) && !table.hasColumnLabel(column.toUpperCase())){
                return false;
            }
        }
        return true;
    }

    /**
     * 得到Model映射的原始表名
     * saas模式下 返回母表表名
     * @return
     */
    public String _getOriginTableName(){
        return tableName;
    }
    /**
     * 得到表名
     *
     * @return
     */
    public String _getTableName() {
        if (!JBoltConfig.SAAS_ENABLE || !_isSeparate() || !JBoltSaasTenantKit.me.isOk()) {
            return _getOriginTableName();
        }
        return _getCurrentTenantTableName();
    }

    private static final String UNDERLINE = "_";
    /**
     * 得到租户表名  处理分表
     *
     * @param tenantId
     * @return
     */
    private String _getTenantTableName(String tenantId) {
        if (StrKit.isBlank(tenantId)) {
            throw new RuntimeException("租户ID tenantId 不能为null");
        }
        return _getOriginTableName() + UNDERLINE + tenantId;
    }

    /**
     * 得到当前租户表名  处理分表
     *
     * @return
     */
    public String _getCurrentTenantTableName() {
        return _getTenantTableName(JBoltSaasTenantKit.me.getIdToStr());
    }

    /**
     * 得到映射表
     */
    @Override
    public Table _getTable() {
        return _getTable(false);
    }

    /**
     * 获取Model映射数据库表
     *
     * @param validateNull
     * @return
     */
    public Table _getTable(boolean validateNull) {
        if (table == null) {
            table = super._getTable();
            if (table == null && validateNull) {
                String msg = String.format(
                        "class %s can not mapping to database table,maybe application cannot connect to database. ",
                        _getUsefulClass().getName());
                LOG.error(msg);
                throw new RuntimeException(msg);
            }
        }
        return table;
    }

    public void setObjectUserId(Object userId) {
        set(COLUMN_USER_ID, userId, true);
    }

    public void setObjectCreateUserId(Object userId) {
        set(COLUMN_CREATE_USER_ID, userId, true);
    }

    public void setObjectUpdateUserId(Object updateUserId) {
        set(COLUMN_UPDATE_USER_ID, updateUserId, true);
    }

    /**
     * 初始化主键
     */
    private M autoSetPrimaryKeyValue() {
        // 如果是NULL
        if (!compositePrimaryKey && null == get(primaryKey)) {
            // 优先查看全局强制策略 如果没有设置强制策略 就使用model上的配置策略
            if (StrKit.notBlank(idGenMode)) {
                switch (idGenMode) {
                    case JBoltIDGenMode.SNOWFLAKE:
                        super.set(primaryKey, JBoltSnowflakeKit.me.nextId());
                        break;
                    case JBoltIDGenMode.SNOWFLAKE_STRING:
                        super.set(primaryKey, JBoltSnowflakeKit.me.nextIdStr());
                        break;
                    case JBoltIDGenMode.UUID:
                        super.set(primaryKey, IdUtil.fastSimpleUUID());
                        break;
                    case JBoltIDGenMode.SEQUENCE:
                        super.set(primaryKey, idSequenceValue);
                        break;
                    case JBoltIDGenMode.SEQUENCE_LONG:
                        super.set(primaryKey, idSequenceValue);
                        break;
                }
            }
        }
        return (M) this;
    }

    /**
     * 检测是否需要自动调用自动处理ID
     *
     * @return
     */
    public boolean checkNeedAutoProcessIdValue() {
        // 存在idGenMode 并且不等 auto
        return !compositePrimaryKey && StrKit.notBlank(idGenMode)
                && !JBoltIDGenMode.AUTO.equals(idGenMode) && !JBoltIDGenMode.AUTO_LONG.equals(idGenMode)
                && !JBoltIDGenMode.AUTO_STRING.equals(idGenMode)
                && null == get(primaryKey);
    }

    /**
     * 检测是否需要自动调用自动处理createUserId
     *
     * @return
     */
    public boolean checkNeedAutoProcessCreateUserIdValue() {
        return hasColumn(COLUMN_CREATE_USER_ID) && null == get(COLUMN_CREATE_USER_ID);
    }

    /**
     * 检测是否需要自动调用自动处理updateUserId
     *
     * @return
     */
    public boolean checkNeedAutoProcessUpdateUserIdValue() {
        return hasColumn(COLUMN_UPDATE_USER_ID) && null == get(COLUMN_UPDATE_USER_ID);
    }

    /**
     * 检测是否需要自动调用自动处理deleteUserId
     *
     * @return
     */
    public boolean checkNeedAutoProcessDeleteUserIdValue() {
        return hasColumn(COLUMN_DELETE_USER_ID) && null == get(COLUMN_DELETE_USER_ID);
    }

    /**
     * 检测是否需要在批量save时调用beforeSave
     *
     * @return
     */
    public boolean checkNeedBeforeSaveInBatchSave() {
        return checkNeedAutoProcessIdValue() || checkNeedAutoProcessCreateUserIdValue()
                || checkNeedAutoProcessUpdateUserIdValue() || checkNeedAutoProcessCreateTime()
                || checkNeedAutoProcessUpdateTime();
    }

    /**
     * 检测是否需要自动处理createTime
     *
     * @return
     */
    public boolean checkNeedAutoProcessCreateTime() {
        return hasColumn(COLUMN_CREATE_TIME) && null == get(COLUMN_CREATE_TIME);
    }

    /**
     * 检测是否需要自动处理updateTime
     *
     * @return
     */
    public boolean checkNeedAutoProcessUpdateTime() {
        return hasColumn(COLUMN_UPDATE_TIME);
    }

    /**
     * 自动判断处理id value值 只有在批量新增的时候 并且不是auto自增模式的时候
     */
    public M autoProcessIdValue() {
        autoSetPrimaryKeyValue();
        return (M) this;
    }

    /**
     * 执行update之前 处理完 返回 updateDbKeyCache 是否执行更新KeyCache
     *
     * @return
     */
    public boolean beforeUpdate() {
        return beforeUpdate(new Date());
    }

    /**
     * 获取批量更新是的key
     *
     * @return
     */
    public String _getBatchUpdateKey() {
        if (StrKit.isBlank(batchUpdateKey)) {
            batchUpdateKey = "m_" + (compositePrimaryKey ? JBoltArrayUtil.join(_getIdValues(), UNDERLINE) : _getIdValue());
        }
        return batchUpdateKey;
    }

    /**
     * 执行update之前 处理完 返回 updateDbKeyCache 是否执行更新KeyCache
     *
     * @return
     */
    public boolean beforeUpdate(Date date) {
        autoProcessUpdateTime(date,false);
        autoProcessUpdateUserId();
        // 这里如果设置按照列处理的缓存，需要修改执行前判断一下数据库数据和提交数据是否列值相同
        // 如果不同就需要把之前的key缓存删除掉
        boolean updateDbKeyCache = false;
        if (isKeyCacheEnable) {
            // 得到修改前的数据
            M m = compositePrimaryKey ? findByIds(_getIdValues()) : findById(_getIdValue());
            if (m != null) {
                // 拿到column的值 对比一下
                Object dbColumnValue = m.get(this.autoCache.column());
                Object currentColumnValue = get(this.autoCache.column());
                // 看看有没有修改过
                if (dbColumnValue != null && StrKit.notBlank(dbColumnValue.toString()) && currentColumnValue != null
                        && StrKit.notBlank(currentColumnValue.toString())
                        && !dbColumnValue.toString().equals(currentColumnValue.toString())) {
                    m.deleteKeyCache();
                    updateDbKeyCache = true;
                }
            }
        }
        return updateDbKeyCache;
    }

    @Override
    public boolean update() {
        boolean updateDbKeyCache = beforeUpdate();
        boolean success = this.superUpdate();
        if (success) {
            afterUpdate(updateDbKeyCache);
        }
        return success;
    }

    /**
     * 执行更新之后的操作
     *
     * @param updateDbKeyCache 是否更新keycache
     */
    public void afterUpdate(boolean updateDbKeyCache) {
        deleteIdCache();
        // 如果key没有改动就不执行了 改动过才执行
        if (isKeyCacheEnable && !updateDbKeyCache) {
            deleteKeyCache();
        }
        // 额外配置缓存删除
        deleteExtraCache("update");
    }

    /**
     * 额外还需要从其它渠道删除的cache处理 需要自行实现
     */
    protected void deleteExtraCache(String action) {
        // TODO 额外需要的地方自行覆写实现
    }

    /**
     * 清空自身相关cache
     */
    public void clearCache() {
        deleteIdCache();
        deleteKeyCache();
        deleteExtraCache("clear");
    }

    @Override
    public boolean delete() {
        if (JBoltConfig.DEMO_MODE) {
            throw new RuntimeException(JBoltMsg.DEMO_MODE_CAN_NOT_DELETE);
        }
        //检测是否可以删除
        String msg = checkCanDelete();
        if(StrKit.notBlank(msg)){
            throw new RuntimeException(msg);
        }

        boolean success = superDelete();
        if (success) {
            deleteIdCache();
            deleteKeyCache();
            // 额外配置缓存删除
            deleteExtraCache("delete");
        }
        return success;
    }

    /**
     * 检测是否能删除
     * @return
     */
    protected String checkCanDelete(){
        // TODO 额外需要的地方自行覆写实现
        return null;
    }

    @Override
    public boolean deleteById(Object idValue) {
        return deleteByIds(idValue);
    }

    /**
     * 获取单主键值
     *
     * @return
     */
    public Object _getIdValue() {
        if (compositePrimaryKey) {
            String msg = String.format("[%s]主键为复合主键 请调用_getIdValues()", _getUsefulClass());
            LOG.error(msg);
            throw new RuntimeException(msg);
        }
        return get(primaryKey);
    }

    /**
     * 获取复合主键 值 数组
     *
     * @return
     */
    public Object[] _getIdValues() {
        if (!compositePrimaryKey) {
            String msg = String.format("[%s]主键不是复合主键 请调用_getIdValue()", _getUsefulClass());
            LOG.error(msg);
            throw new RuntimeException(msg);
        }
        int len = compositePrimaryKeys.length;
        Object[] values = new Object[len];
        for (int i = 0; i < len; i++) {
            values[i] = get(compositePrimaryKeys[i]);
        }
        return values;
    }

    /**
     * 得到配置keyCahce的对应column值
     *
     * @param <T>
     * @return
     */
    public <T> T _getKeyValue() {
        if (!isKeyCacheEnable) {
            return null;
        }
        String column = this.autoCache.column();
        if(!containsAttr(column)){
            Object id = _getIdValue();
            if(JBoltParaValidator.isOk(id)){
                JBoltBaseModel<M> m = findById(id);
                if(m != null){
                    return m.get(column);
                }
            }
        }
        return get(column);
    }

    public boolean containsAttr(String attrName) {
        return this._getAttrs().containsKey(attrName);
    }

    /**
     * 得到配置KeyCache后对应的bingColumn值
     *
     * @param <T>
     * @return
     */
    public <T> T _getKeyBindColumnValue() {
        if (!isKeyCacheEnable) {
            return null;
        }
        String bindColumn = this.autoCache.bindColumn();
        if(!containsAttr(bindColumn)){
            Object id = _getIdValue();
            if(JBoltParaValidator.isOk(id)){
                JBoltBaseModel<M> m = findById(id);
                if(m != null){
                    return m.get(bindColumn);
                }
            }
        }
        return get(bindColumn);
    }

    /**
     * 判断是否开启了按ID-Object规则操作缓存
     *
     * @return
     */
    public boolean _isIdCacheEnable() {
        return isIdCacheEnable;
    }

    /**
     * 判断是否使用了JBoltAutoCache注解
     *
     * @return
     */
    public boolean _isAutoCache() {
        return isAutoCacheEnable;
    }

    /**
     * 判断是否开启了按Key-Object规则操作缓存
     *
     * @return
     */
    public boolean _isKeyCacheEnable() {
        return isKeyCacheEnable;
    }

    @Override
    public boolean deleteByIds(Object... idValues) {
        M m = findByIds(idValues);
        if (m == null) {
            return false;
        }
        return m.delete();
    }

    /**
     * 根据主键删除缓存
     */
    public void deleteIdCache() {
        if (isIdCacheEnable) {
            if (compositePrimaryKey) {
                deleteCacheById(_getIdValues());
            } else {
                deleteCacheById(_getIdValue());
            }
        }
    }

    /**
     * 根据key删除缓存
     */
    public void deleteKeyCache() {
        deleteCacheByKey(_getKeyValue(), _getKeyBindColumnValue());
    }

    /**
     * 根据主键删除缓存
     *
     * @param idValues
     */
    public void deleteCacheById(Object... idValues) {
        if (isIdCacheEnable) {
            safeDeleteByCacheKey(buildIdCacheKey(idValues));
        }
    }

    /**
     * 根据Key删除缓存
     *
     * @param columValue
     * @param bindColumnValue
     */
    public void deleteCacheByKey(Object columValue, Object bindColumnValue) {
        if (isKeyCacheEnable) {
            safeDeleteByCacheKey(buildKeyCacheKey(columValue, bindColumnValue));
        }
    }

    /**
     * 根据ID获取缓存数据
     *
     * @param idValues
     * @return
     */
    private M loadCacheById(Object... idValues) {
        if (isIdCacheEnable) {
            try {
                String cacheKey = buildIdCacheKey(idValues);
                M m = JBoltCacheKit.get(this.autoCache.name(), cacheKey, new IDataLoader() {
                    @Override
                    public Object load() {
                        M m = superFindByIds(idValues);
                        if (m instanceof User) {
                            m.remove("password", "pwd_salt");
                        }
                        return m;
                    }
                });
                if (JBoltConfig.JBOLT_AUTO_CACHE_LOG) {
                    JBOLT_AUTO_CACHE_LOG.debug("JBolt loadCacheById Result: cacheKey={}\n{}", cacheKey,
                            JSON.toJSONString(m, true));
                }
                //从缓存拿到可能没有初始化
                if (m != null) {
                    m._init();
                }
                return m;
            } catch (Exception ex) {
                JBOLT_AUTO_CACHE_LOG.error(ex.toString(), ex);
                deleteCacheById(idValues);
            }
        }
        return superFindByIds(idValues);
    }

    private M superFindByIds(Object[] idValues) {
        if (idValues == null || idValues.length == 0) {
            return null;
        }
        if (idValues.length == 1) {
            return superFindById(idValues[0]);
        }
        return super.findByIds(idValues);
    }

    /**
     * 根据注解指定的column值做key 处理获取缓存数据
     *
     * @param columnValue
     * @return
     */
    public M loadCacheByKey(String columnValue) {
        return loadCacheByKey(columnValue, null);
    }

    /**
     * 据注解指定的column和bindColumn做key 处理获取缓存数据
     *
     * @param columnValue
     * @param bindColumnValue
     * @return
     */
    public M loadCacheByKey(String columnValue, Object bindColumnValue) {
        if (this.autoCache == null || StrKit.isBlank(this.autoCache.column()) || StrKit.isBlank(columnValue)) {
            return null;
        }
        try {
            String cacheKey = buildKeyCacheKey(columnValue, bindColumnValue);
            M m = JBoltCacheKit.get(this.autoCache.name(), cacheKey, new IDataLoader() {
                @Override
                public Object load() {
                    M m = findFirstByColumn(autoCache.column(), columnValue, autoCache.bindColumn(), bindColumnValue);
                    if (m instanceof User) {
                        m.remove("password", "pwd_salt");
                    }
                    return m;
                }
            });
            if (JBoltConfig.JBOLT_AUTO_CACHE_LOG) {
                JBOLT_AUTO_CACHE_LOG.debug("JBolt loadCacheByKey Result: cacheKey={}\n{}", cacheKey,
                        JSON.toJSONString(m, true));
            }
            //从缓存拿到可能没有初始化
            if (m != null) {
                m._init();
            }
            return m;
        } catch (Exception ex) {
            JBOLT_AUTO_CACHE_LOG.error(ex.toString(), ex);
            deleteCacheByKey(columnValue, bindColumnValue);
        }
        return findFirstByColumn(autoCache.column(), columnValue, autoCache.bindColumn(), bindColumnValue);
    }

    /**
     * 根据指定列值获取唯一数据
     *
     * @param column
     * @param columnValue
     * @return
     */
    protected M findFirstByColumn(String column, String columnValue, String bindColumn, Object bindColumnValue) {
        String tableName = _getTableName();
        Sql sql = Sql.me(dbType).select().from(tableName).eq(column, columnValue).first();
        if (StrKit.notBlank(bindColumn)) {
            if (bindColumnValue == null || StrKit.isBlank(bindColumnValue.toString())) {
                String msg = String.format("JBoltAutoCache bindColumn [%s], Value is null in [%s]", bindColumn,
                        _getUsefulClass());
                JBOLT_AUTO_CACHE_LOG.error(msg);
                throw new RuntimeException(msg);
            } else {
                sql.eq(bindColumn, bindColumnValue);
            }
        }

        return findFirst(sql.toSql());
    }

    @Override
    public M findById(Object idValue) {
        if (idValue == null) {
            return null;
        }
        return isIdCacheEnable ? loadCacheById(idValue) : superFindById(idValue);
    }

    @Override
    public M findByIds(Object... idValues) {
        if (idValues == null || idValues.length == 0) {
            return null;
        }
        if (compositePrimaryKey && idValues.length != compositePrimaryKeys.length) {
            throw new IllegalArgumentException("idValues个数与复合主键个数不符 应为:" + compositePrimaryKeys.length);
        }
        if (!compositePrimaryKey && idValues.length != 1) {
            throw new IllegalArgumentException("主键只有一个 idValues个数必须为1");
        }
        return isIdCacheEnable ? loadCacheById(idValues) : super.findByIds(idValues);
    }

    /**
     * 根据最终的key删除 idCache和KeyCache都可以调用
     *
     * @param deleteKey
     */
    private void safeDeleteByCacheKey(String deleteKey) {
        if (isAutoCacheEnable && StrKit.notBlank(deleteKey)) {
            try {
                if (JBoltConfig.JBOLT_AUTO_CACHE_LOG) {
                    JBOLT_AUTO_CACHE_LOG.debug("delete Cache Key:" + deleteKey);
                }
                JBoltCacheKit.remove(this.autoCache.name(), deleteKey);
            } catch (Exception ex) {
                JBOLT_AUTO_CACHE_LOG.error(ex.toString(), ex);
            }
        }
    }

    /**
     * 拼接Id CACHE KEY
     *
     * @param idValues
     * @return
     */
    private String buildIdCacheKey(Object... idValues) {
        if (!isIdCacheEnable || idValues == null || idValues.length == 0) {
            return null;
        }
        String name = _getTableName();
        StringBuilder key = new StringBuilder();
        key.append(this.autoCache.prefix()).append(name).append(UNDERLINE);
        for (int i = 0; i < idValues.length; i++) {
            key.append(idValues[i]);
            if (i < idValues.length - 1) {
                key.append(UNDERLINE);
            }
        }
        if (JBoltConfig.JBOLT_AUTO_CACHE_LOG) {
            JBOLT_AUTO_CACHE_LOG.debug("buildIdCacheKey result:[{}]", key);
        }
        return key.toString();
    }

    /**
     * 拼接Key CACHE KEY
     *
     * @param columnValue
     * @return
     */
    private String buildKeyCacheKey(Object columnValue, Object bindColumnValue) {
        if (this.autoCache == null || StrKit.isBlank(this.autoCache.column()) || columnValue == null
                || StrKit.isBlank(columnValue.toString())) {
            return null;
        }
        String name = _getTableName();
        StringBuilder keySb = new StringBuilder();
        keySb.append(this.autoCache.prefix()).append(name).append(UNDERLINE).append(this.autoCache.column()).append(UNDERLINE)
                .append(columnValue);
        // 如果额外绑定了其它字段 需要拼接上
        String bindColumn = this.autoCache.bindColumn();
        if (StrKit.notBlank(bindColumn)) {
            if (bindColumnValue != null && StrKit.isBlank(bindColumnValue.toString())) {
                String msg = String.format("JBoltAutoCache bindColumn [%s], Value is null in [%s]", bindColumn,
                        _getUsefulClass());
                JBOLT_AUTO_CACHE_LOG.error(msg);
                throw new RuntimeException(msg);
            }
            keySb.append(UNDERLINE).append(bindColumnValue);
        }
        if (JBoltSaasTenantKit.me.isSelfRequest()) {
            keySb.append(UNDERLINE).append(JBoltSaasTenantKit.me.getSn());
        }
        if (JBoltConfig.JBOLT_AUTO_CACHE_LOG) {
            JBOLT_AUTO_CACHE_LOG.debug("buildKeyCacheKey result:[{}]", keySb);
        }
        return keySb.toString();
    }

    /**
     * Model原始update
     *
     * @return
     */
    protected boolean superUpdate() {
        return super.update();
    }

    /**
     * Model原始findById
     *
     * @return
     */
    public M superFindById(Object id) {
        if (id instanceof Long || id instanceof Integer) {
            return super.findById(id);
        }
        switch (idGenMode) {
            case JBoltIDGenMode.SNOWFLAKE:
                return super.findById(Long.parseLong(id.toString()));
            case JBoltIDGenMode.SNOWFLAKE_STRING:
                return super.findById(id.toString());
            case JBoltIDGenMode.AUTO:
                return super.findById(Integer.parseInt(id.toString()));
            case JBoltIDGenMode.AUTO_LONG:
                return super.findById(Long.parseLong(id.toString()));
            case JBoltIDGenMode.AUTO_STRING:
                return super.findById(id.toString());
            case JBoltIDGenMode.SERIAL:
                return super.findById(Integer.parseInt(id.toString()));
            case JBoltIDGenMode.BIGSERIAL:
                return super.findById(Long.parseLong(id.toString()));
            case JBoltIDGenMode.UUID:
                return super.findById(id.toString());
            case JBoltIDGenMode.SEQUENCE:
                return super.findById(Integer.parseInt(id.toString()));
            case JBoltIDGenMode.SEQUENCE_LONG:
                return super.findById(Long.parseLong(id.toString()));
            case JBoltIDGenMode.ASSIGN_STRING:
                return super.findById(id.toString());
            case JBoltIDGenMode.ASSIGN_INT:
                return super.findById(Integer.parseInt(id.toString()));
            case JBoltIDGenMode.ASSIGN_LONG:
                return super.findById(Long.parseLong(id.toString()));
        }
        return super.findById(id);
    }

    protected String getTableColumnStrWithout(String... withoutColumns) {
        String[] selectColumns = getTableColumnsWithout(withoutColumns);
        if(selectColumns == null || selectColumns.length == 0){
            return null;
        }
        return JBoltArrayUtil.join(selectColumns);
    }


    protected String[] getTableColumnsWithout(String... withoutColumns) {
        if (withoutColumns == null || withoutColumns.length == 0) {
            return null;
        }
        if(withoutColumns.length == 1 && withoutColumns[0].contains(",")){
            withoutColumns = JBoltArrayUtil.from3(withoutColumns[0]);
        }
        HashSet<String> tableColumns = new HashSet<>(_getTable().getColumnNameSet());
        for (String col : withoutColumns) {
            tableColumns.remove(col.trim());
            tableColumns.remove(col.trim().toLowerCase());
            tableColumns.remove(col.trim().toUpperCase());
        }
        return tableColumns.size() == 0 ? null : tableColumns.toArray(new String[0]);
    }

    @Override
    public M findByIdLoadColumns(Object id, String columns) {
        if(_isIdCacheEnable()){
            M m = findById(id);
            if(m == null){
                m = super.findByIdLoadColumns(id, columns);
            }
//            if(columns!=null && columns.trim().length() > 0){
//                if(!JBoltCacheType.REDIS.equals(JBoltConfig.JBOLT_CACHE_TYPE)){
//                   m = (M) BeanUtil.mapToBean(m.toMap(),this.getClass(),true);
//                }
//                m.remove(getTableColumnsWithout(columns));
//            }
            return m;
        }
        return superFindByIdLoadColumns(id, columns);
    }

    public M superFindByIdLoadColumns(Object id, String columns) {
        if (id instanceof Long || id instanceof Integer) {
            return super.findByIdLoadColumns(id, columns);
        }
        switch (idGenMode) {
            case JBoltIDGenMode.SNOWFLAKE:
                return super.findByIdLoadColumns(Long.parseLong(id.toString()), columns);
            case JBoltIDGenMode.SNOWFLAKE_STRING:
                return super.findByIdLoadColumns(id.toString(), columns);
            case JBoltIDGenMode.AUTO:
                return super.findByIdLoadColumns(Integer.parseInt(id.toString()), columns);
            case JBoltIDGenMode.AUTO_LONG:
                return super.findByIdLoadColumns(Long.parseLong(id.toString()), columns);
            case JBoltIDGenMode.AUTO_STRING:
                return super.findByIdLoadColumns(id.toString(), columns);
            case JBoltIDGenMode.SERIAL:
                return super.findByIdLoadColumns(Integer.parseInt(id.toString()), columns);
            case JBoltIDGenMode.BIGSERIAL:
                return super.findByIdLoadColumns(Long.parseLong(id.toString()), columns);
            case JBoltIDGenMode.UUID:
                return super.findByIdLoadColumns(id.toString(), columns);
            case JBoltIDGenMode.SEQUENCE:
                return super.findByIdLoadColumns(Integer.parseInt(id.toString()), columns);
            case JBoltIDGenMode.SEQUENCE_LONG:
                return super.findByIdLoadColumns(Long.parseLong(id.toString()), columns);
            case JBoltIDGenMode.ASSIGN_STRING:
                return super.findByIdLoadColumns(id.toString(), columns);
            case JBoltIDGenMode.ASSIGN_INT:
                return super.findByIdLoadColumns(Integer.parseInt(id.toString()), columns);
            case JBoltIDGenMode.ASSIGN_LONG:
                return super.findByIdLoadColumns(Long.parseLong(id.toString()), columns);
        }
        return super.findByIdLoadColumns(id, columns);
    }

    /**
     * 自动处理CreateTIme
     */
    protected void autoProcessCreateTime() {
        autoProcessCreateTime(new Date());
    }

    /**
     * 自动处理CreateTIme
     */
    protected void autoProcessCreateTime(Date date) {
        if (hasColumn(COLUMN_CREATE_TIME) && null == get(COLUMN_CREATE_TIME)) {
            set(COLUMN_CREATE_TIME, date);
        }
    }

    /**
     * 自动处理createUserId
     */
    protected void autoProcessCreateUserId() {
        if (checkNeedAutoProcessCreateUserIdValue()) {
            set(COLUMN_CREATE_USER_ID, JBoltUserKit.getUserId());
        }
    }

    /**
     * 自动处理updateUserId
     */
    protected void autoProcessUpdateUserId() {
        if (checkNeedAutoProcessUpdateUserIdValue()) {
            set(COLUMN_UPDATE_USER_ID, JBoltUserKit.getUserId());
        }
    }

    /**
     * 自动处理DeleteUserId
     */
    public void autoProcessDeleteUserId() {
        if (checkNeedAutoProcessDeleteUserIdValue()) {
            set(COLUMN_DELETE_USER_ID, JBoltUserKit.getUserId());
        }
    }

    /**
     * 自动处理DeleteTime
     */
    public void autoProcessDeleteTime() {
        if (hasColumn(COLUMN_DELETE_TIME)) {
            set(COLUMN_DELETE_TIME, new Date());
        }
    }

    /**
     * 自动处理updateTime
     * @param checkIsNull
     */
    private void autoProcessUpdateTime(boolean checkIsNull) {
        autoProcessUpdateTime(new Date(),checkIsNull);
    }

    /**
     * 处理updateTime
     *
     * @param date
     * @param checkIsNull
     */
    private void autoProcessUpdateTime(Date date,boolean checkIsNull) {
        if (hasColumn(COLUMN_UPDATE_TIME)) {
            if(checkIsNull){
                if(null == get(COLUMN_UPDATE_TIME)){
                    set(COLUMN_UPDATE_TIME, date);
                }
            }else{
                if(!this._getModifyFlag().contains(COLUMN_UPDATE_TIME) && !this._getModifyFlag().contains(COLUMN_UPDATE_TIME.toUpperCase())){
                    set(COLUMN_UPDATE_TIME, date);
                }
            }
        }
    }

    /**
     * save时先行处理操作
     *
     * @return
     */
    private void beforeSave() {
        autoProcessCreateTime();
        autoProcessUpdateTime(true);
        autoProcessCreateUserId();
        autoProcessUpdateUserId();
        autoProcessIdValue();
    }

    /**
     * save时先行处理操作
     *
     * @return
     */
    public void beforeSaveInBatchSave(Date date) {
        autoProcessCreateTime(date);
        autoProcessUpdateTime(date,true);
        autoProcessCreateUserId();
        autoProcessUpdateUserId();
        autoProcessIdValue();
    }

    /**
     * save之后处理
     */
    public void afterSave() {
        // 处理额外相关缓存的删除
        deleteExtraCache("save");
    }

    @Override
    public boolean save() {
        beforeSave();
        boolean success = this.superSave();
        if (success) {
            afterSave();
        }
        return success;
    }

    @Override
    protected Config _getConfig() {
        String configName = _getDataSourceConfigName();
        if (configName != null) {
           return DbKit.getConfig(configName);
        }
        return DbKit.getConfig(_getUsefulClass());
    }

    /**
     * Model原始Save
     *
     * @return
     */
    protected boolean superSave() {
        filter(FILTER_BY_SAVE);

        Config config = _getConfig();
        Table table = _getTable();

        StringBuilder sql = new StringBuilder();
        List<Object> paras = new ArrayList<Object>();
        config.getDialect().forModelSave(table, _getAttrs(), sql, paras);
        // if (paras.size() == 0) return false; // The sql "insert into tableName()
        // values()" works fine, so delete this line

        // --------
        Connection conn = null;
        PreparedStatement pst = null;
        int result = 0;
        try {
            conn = config.getConnection();
            if (config.getDialect() instanceof OracleDialect || config.getDialect() instanceof PostgreSqlDialect) {
                pst = conn.prepareStatement(sql.toString(), table.getPrimaryKey());
            } else {
                pst = conn.prepareStatement(sql.toString(), Statement.RETURN_GENERATED_KEYS);
            }
            config.getDialect().fillStatement(pst, paras);
            result = pst.executeUpdate();
            config.getDialect().getModelGeneratedKey(this, pst, table);
            _getModifyFlag().clear();
            return result >= 1;
        } catch (Exception e) {
            throw new ActiveRecordException(e);
        } finally {
            config.close(pst, conn);
        }
    }

    /**
     * Model原始delete
     *
     * @return
     */
    protected boolean superDelete() {
        return super.delete();
    }

    /**
     * 设置子关联数据
     *
     * @param items
     */
    public void putItems(List<?> items) {
        if (items != null && items.size() > 0) {
            put("items", items);
        }
    }

    /**
     * 添加到items中
     * @param item
     */
    public void addItem(Object item) {
         List<Object> items = getItems();
         if(items == null){
             items = new ArrayList<>();
             items.add(item);
             putItems(items);
         }else{
             items.add(item);
         }
    }

    /**
     * 得到关联子数据
     *
     * @return
     */
    public <T> List<T> getItems() {
        return this.get("items");
    }

    /**
     * 得到循环层级 树形类表结构才有用
     *
     * @return
     */
    public Integer getEachLevel() {
        return getInt("eachLevel");
    }

    /**
     * 设置循环层级 树形类表结构才有用
     *
     * @param eachLevel
     */
    public void putEachLevel(Integer eachLevel) {
        this.put("eachLevel", eachLevel);
    }

    /**
     * 设置循环层级 根据父节点的eachLevel处理 树形类表结构才有用
     *
     * @param parentLevel
     */
    public void processEachLevelByParentLevel(Integer parentLevel) {
        putEachLevel(parentLevel == null ? 1 : parentLevel + 1);
    }

    /**
     * 检测是否是更新
     *
     * @param action
     * @return
     */
    protected boolean _actionIsUpdate(String action) {
        return action.equals("update");
    }

    /**
     * 检测是否是保存
     *
     * @param action
     * @return
     */
    protected boolean _actionIsSave(String action) {
        return action.equals("save");
    }

    /**
     * 检测是否是删除
     *
     * @param action
     * @return
     */
    protected boolean _actionIsDelete(String action) {
        return action.equals("delete");
    }

    /**
     * 获取数据源配置name
     *
     * @return
     */
    public String _getDataSourceConfigName() {
        if (!JBoltConfig.SAAS_ENABLE || !_isSeparate() || !JBoltSaasTenantKit.me.isOk()) {
            if(dataSource == null){
                dataSource = DbKit.MAIN_CONFIG_NAME;
            }
            return dataSource;
        }
        return JBoltConfig.SAAS_TENANT_DATASOURCE_CONFIG_NAME;
    }

    /**
     * 获取数据源数据库名称
     *
     * @return
     */
    public String _getDatabaseName() {
        return database;
    }

    /**
     * 获取数据源数据库模式名称
     *
     * @return
     */
    public String _getSchema() {
        return schema;
    }

    /**
     * 获取数据源dbType
     *
     * @return
     */
    public String _getDbType() {
        return dbType;
    }

    /**
     * 获取数据源separate
     *
     * @return
     */
    public boolean _isSeparate() {
        return separate;
    }

    /**
     * 获取id生成策略
     *
     * @return
     */
    public String _getIdGenMode() {
        return idGenMode;
    }
}
